/**
 * @file      log4x.h
 * @copyright Copyright (c) 2017, ZCW Co., Ltd. All Rights Reserved.
 * @brief     log module such as log4j in Java
 * @author    caiwang213@qq.com
 * @date      2017-11-21 10:08:58
 *
 * @note
 *  log4x.h defines
 */
#ifndef __LOG4X_H__
#define __LOG4X_H__

#ifdef __ANDROID__
#define LOG4X_DEFAULT_PATH             ("/storage/emulated/0/log4x/")
#else
#define LOG4X_DEFAULT_PATH             ("./log/")
#endif
#define LOG4X_DEFAULT_LEVEL            (LOG_LEVEL_DEBUG)
#define LOG4X_DEFAULT_DISPLAY          (true)

#define LOG4X_DEFAULT_OUTFILE          (true)
#define LOG4X_DEFAULT_MONTHDIR         (true)
#define LOG4X_DEFAULT_LIMITSIZE        (20)
#define LOG4X_DEFAULT_FILELINE         (true)
#define LOG4X_DEFAULT_RESERVE          (7*24*3600)
#define LOG4X_DEFAULT_RESERVE_FILES    (20)

#define LOG4X_LOG_KEY_MAX              (20)
#define LOG4X_LOG_QUEUE_LIMIT_SIZE     (500)

#ifndef LOG4X_MACRO
#define LOG4X_MACRO
#define LOG_FORMAT(key, level, func, file, line, sync, logformat, ...) \
do { \
    log4x::ilog4x::instance()->push(key, level, func, file, line, sync, logformat, ##__VA_ARGS__); \
}while(0)

#define LOG_LEVEL(key, level) \
do \
{ \
    log4x::ilog4x::instance()->setLevel(key, level); \
} while (0)

#define LOG_RELEASE_LEVEL(key, level) \
do \
{ \
    if (!log4x::ilog4x::instance()->isff(key)) \
    { \
        LOG_LEVEL(key, level); \
    } \
} while (0)

#ifdef _DEBUG
/* setlevel > fromfile for debug version */
#define LOG4X_DEBUG_LEVEL(key, level) LOG_LEVEL(key, level)
#else
/* fromfile > setlevel for release version*/
#define LOG4X_DEBUG_LEVEL(key, level) LOG_RELEASE_LEVEL(key, level)
#endif

/* format string */
#define LOGF_TRACE(key, fmt, ...)  LOG_FORMAT(key, LOG_LEVEL_TRACE, __FUNCTION__, __FILE__, __LINE__, false, fmt, ##__VA_ARGS__)
#define LOGF_DEBUG(key, fmt, ...)  LOG_FORMAT(key, LOG_LEVEL_DEBUG, __FUNCTION__, __FILE__, __LINE__, false, fmt, ##__VA_ARGS__)
#define LOGF_INFO(key,  fmt, ...)  LOG_FORMAT(key, LOG_LEVEL_INFO,  __FUNCTION__, __FILE__, __LINE__, false, fmt, ##__VA_ARGS__)
#define LOGF_WARN(key,  fmt, ...)  LOG_FORMAT(key, LOG_LEVEL_WARN,  __FUNCTION__, __FILE__, __LINE__, false, fmt, ##__VA_ARGS__)
#define LOGF_ERROR(key, fmt, ...)  LOG_FORMAT(key, LOG_LEVEL_ERROR, __FUNCTION__, __FILE__, __LINE__, false, fmt, ##__VA_ARGS__)
#define LOGF_FATAL(key, fmt, ...)  LOG_FORMAT(key, LOG_LEVEL_FATAL, __FUNCTION__, __FILE__, __LINE__, true , fmt, ##__VA_ARGS__)

#define LOGFT(fmt, ...) LOGF_TRACE("main", fmt, ##__VA_ARGS__)
#define LOGFD(fmt, ...) LOGF_DEBUG("main", fmt, ##__VA_ARGS__)
#define LOGFI(fmt, ...) LOGF_INFO ("main", fmt, ##__VA_ARGS__)
#define LOGFW(fmt, ...) LOGF_WARN ("main", fmt, ##__VA_ARGS__)
#define LOGFE(fmt, ...) LOGF_ERROR("main", fmt, ##__VA_ARGS__)
#define LOGFF(fmt, ...) LOGF_FATAL("main", fmt, ##__VA_ARGS__)

#define THROW_ERROR(key, fmt, ...) \
do \
{ \
    LOG_FORMAT(key, LOG_LEVEL_ERROR, __FUNCTION__, __FILE__, __LINE__, false, fmt, ##__VA_ARGS__); \
    char dest[4096] = {0}; \
    int  count = 4096; \
    log4x::format(dest, count, "[%s:%s:%s:%d]" fmt, key, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); \
    throw std::runtime_error(dest); \
} while (0)

#if defined(LOG4X_STATICLIB)
#  define LOG4X_API
#elif defined(WIN32) || defined(_WIN32)
#  if defined(LOG4X_EXPORTS)
#    define LOG4X_API  __declspec(dllexport)
#  else
#    define LOG4X_API  __declspec(dllimport)
#  endif
#else
#  define   LOG4X_API  __attribute__((visibility("default")))
#endif

enum
{
    LOG_LEVEL_TRACE = 0,
    LOG_LEVEL_DEBUG,
    LOG_LEVEL_INFO,
    LOG_LEVEL_WARN,
    LOG_LEVEL_ERROR,
    LOG_LEVEL_FATAL,
};
#endif

namespace log4x
{
struct log4x_t;


/**
 * @brief 日志接口类
 */
class ilog4x
{
public:
    /**
     * @brief 读取配置文件
     *
     * @param [in] path 配置文件路径
     *
     * @return 成功返回0. 失败返回-1.
     *
     * @see configFromString
     */
    virtual int config(const char * path) = 0;

    /**
     * @brief 读取配置参数字符串
     *
     * @param [in] str 配置参数字符串
     *
     * @return 成功返回0. 失败返回-1.
     *
     * @see config
     */
    virtual int configFromString(const char * str) = 0;

    /**
     * @brief 启动日志服务
     *
     * @return 成功返回0. 失败返回-1.
     *
     * @see stop
     */
    virtual int start() = 0;

    /**
     * @brief 停止日志服务
     *
     * @see start
     */
    virtual void stop()  = 0;

    /**
     * @brief 写日志信息
     *
     * @param [in] key   日志模块名
     * @param [in] level 日志等级
     * @param [in] func  打印信息对应函数名
     * @param [in] file  打印信息对应文件名
     * @param [in] line  打印信息对应行号
     * @param [in] sync  是否同步打印
     * @param [in] fmt   打印格式化字符串
     * @param [in] ...   打印信息
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual int push(const char * key, int level, const char * func, const char * file, int line, bool sync, const char* fmt, ...) = 0;

    /**
     * @brief 使能模块名为key的日志打印
     *
     * @param [in] key    日志模块名
     * @param [in] enable 是否使能
     *                    - true  打开
     *                    - false 关闭
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual int enable(const char * key, bool enable) = 0;

    /**
     * @brief 设置模块日志存放路径
     *
     * @param [in] key  日志模块名
     * @param [in] path 日志存放的路径, 默认为运行目录下./log文件夹中
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual int setPath(const char * key, const char * path) = 0;

    /**
     * @brief 设置模块日志打印等级
     *
     * @param [in] key   日志模块名
     * @param [in] level 日志等级
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual int setLevel(const char * key, int level) = 0;

    /**
     * @brief 是否打印模块日志文件行号
     *
     * @param [in] key    日志模块名
     * @param [in] enable 是否使能, 默认打开
     *                    - true  打开
     *                    - false 关闭
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual int setFileLine(const char * key, bool enable) = 0;

    /**
     * @brief 是否打印模块日志到终端显示屏上
     *
     * @param [in] key    日志模块名
     * @param [in] enable 是否使能, 默认打开
     *                    - true  打开
     *                    - false 关闭
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual int setDisplay(const char * key, bool enable) = 0;

    /**
     * @brief 是否将模块日志输出到日志文件
     *
     * @param [in] key    日志模块名
     * @param [in] enable 是否使能, 默认打开
     *                    - true  打开
     *                    - false 关闭
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual int setOutFile(const char * key, bool enable) = 0;

    /**
     * @brief 设置模块日志单个文件最大值
     *
     * @param [in] key       日志模块名
     * @param [in] limitsize 单个日志文件最大值, 默认为20, 单位为M(兆字节)
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual int setLimitsize(const char * key, unsigned int limitsize) = 0;

    /**
     * @brief 是否将在模块日志输出文件夹中默认创建月份文件夹, 如(2019-07)
     *
     * @param [in] key    日志模块名
     * @param [in] enable 是否使能, 默认打开
     *                    - true  打开
     *                    - false 关闭
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual int setMonthdir(const char * key, bool enable) = 0;

    /**
     * @brief 自动清理日志最少保留日志时间长度
     *
     * @param [in] key 日志模块名
     * @param [in] sec 少保留日志时间长度, 单位为秒, 默认24*3600
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual int setReserve(const char * key, unsigned int sec) = 0;

    /**
     * @brief 是否将在模块日志输出到进程日志文件中(同时打印到模块日志与进程日志中)
     *
     * @param [in] key    日志模块名
     * @param [in] enable 是否使能, 默认打开
     *                    - true  打开
     *                    - false 关闭
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual int setProcFile(const char * key, bool enable) = 0;

    /**
     * @brief 设置自动检测配置文件是否修改时间间隔
     *
     * @param [in] interval 检测配置文件是否修改时间间隔, 位为秒, 默认30
     *
     * @return 成功返回0. 失败返回-1.
     */
    virtual void setAutoUpdate(int interval) = 0;

    virtual bool isff(const char * key) = 0;
public:
    /**
     * @brief 返回日志实例(单例)对象指针
     *
     * @return 成功返回日志实例对象指针. 失败返回NULL.
     */
    LOG4X_API static ilog4x * instance();
    LOG4X_API static ilog4x & reference();
};

LOG4X_API int format(char *dest, int n, const char* fmt, ...);
}

#endif
